package peerToPeerFacebook.proj;

import java.io.IOException;
import java.io.Serializable;

import peerToPeerFacebook.src.edu.washington.cs.cse490h.lib.Utility;

/**
 * This class implements the role of the Acceptor in the Paxos Implementation. 
 */
public class PaxosAcceptor
{
    /** The Facebook Node reference **/
    FBNode node;
    
    /** The suffix for the last Accepted Proposal No file of this Acceptor */
    private String LastAcceptedProposalNoSuffix = "-LastAcceptedProposalNo.txt";
    
    /** The suffix for the last Accepted Proposal Value file of this Acceptor */
    private String LastAcceptedProposalValueSuffix = "-LastAcceptedProposalValue.txt";
    
    /** The suffix for the last proposal Number file of this Acceptor */ 
    private String LastRespondedProposalSuffix = "-LastRespondedProposal.txt";
    
    /** The file prefix for paxos instance */ 
    private String PaxosInstanceFilePrefix = "PaxosInstance-";
    
    /** The integer instance No which represents the current paxos instance **/
    private int newInstanceNo;
    
    /** The String proposalNo of the current paxos instance **/
    private String newProposalNo;
    
   /**
     * The constructor for this class which takes 
     * facebook node class reference
     * 
     * @param node, FBNode reference
     */
    public PaxosAcceptor(FBNode node)
    {
        this.node = node;
    }
    
    /** This method is called by PaxosProposer when they want to start a new
     * round of paxos algorithm with a new proposalNo.
     * This method returns an acceptance to not respond to any proposals numbered
     * less than proposalNo and the last accepted proposal if any.
     * 
     * @param proposalNo, the proposalNo of the new proposal
     * @return the PrepareResponse, with the last accepted proposal (if any) and the promise
     */
    public PrepareResponse prepare(int newInstanceNo, String newProposalNo)
    {
        assert newInstanceNo >= 0 : "The instance No of the paxos should be greater than 0";
        assert newProposalNo != null : "The proposal No of the paxos should not be null";
        
        this.newInstanceNo = newInstanceNo;
        this.newProposalNo = newProposalNo;
        
        return checkLastProposal(); 
    }
    
    /**
     * This method checks the last Accepted Proposal by the acceptor
     * and responds back accordingly.
     * @return the response message
     */
    private PrepareResponse checkLastProposal()
    {
        // Needs to check if the Responded proposal file for the corresponding paxos instance already exists
        if( Utility.fileExists(this.node, PaxosInstanceFilePrefix + newInstanceNo + LastRespondedProposalSuffix))
        {
            String proposalNo;
            PaxosCommand acceptedValue = null;
            String acceptedProposalNo = null;
            PrepareResponse response;
            
            proposalNo = UtilityFile.readFromFile(this.node,  PaxosInstanceFilePrefix + newInstanceNo + LastRespondedProposalSuffix );
            assert proposalNo != null && !proposalNo.equals(""): "The proposal No should not be empty or null";
          
              if (Utility.fileExists(node, PaxosInstanceFilePrefix + newInstanceNo + LastAcceptedProposalNoSuffix))
                  acceptedProposalNo = UtilityFile.readFromFile(this.node,  PaxosInstanceFilePrefix + newInstanceNo +
                            LastAcceptedProposalNoSuffix );
              //System.out.println("\n\n\n The last accepted proposal No for instance No:" + newInstanceNo + " is: " + acceptedProposalNo + "\n\n\n");         
            try
            {
                // it is possible that acceptedValue is null (i.e. we have received prepare request, but not accept)
                // and this is fine. In such cases we just send this value to client. Depend if we return promise or not
                // client may or may not be able to suggest its own value. It is interesting to note that if we return
                // that we are able to promise in this case, this may violate liveliness property of Paxos - such
                // situation most likely signals that there is another proposer that is competing.
                if (Utility.fileExists(node, PaxosInstanceFilePrefix + newInstanceNo + LastAcceptedProposalValueSuffix))
                    acceptedValue = UtilityFile.readFromDisk(this.node,  PaxosInstanceFilePrefix + newInstanceNo +
                            LastAcceptedProposalValueSuffix );
                //System.out.println("\n\n\n The last accepted proposal Value for instance No:" + newInstanceNo + " is: " + acceptedValue + "\n\n\n");
            }
            catch (IOException e)
            {
              System.out.println(" Caught I/O exception while reading from the file:" +  PaxosInstanceFilePrefix + newInstanceNo + LastAcceptedProposalValueSuffix);
            }
             
            // if the new proposal no is greater than the one responded last time, then we can return a promise
            if(new ProposalComparator().compare(newProposalNo, proposalNo) > 0)
            {
                UtilityFile.writeToFile(node, PaxosInstanceFilePrefix + newInstanceNo + LastRespondedProposalSuffix, newProposalNo);
                // since the new proposal No is greater than the last responded, lets return the promise to not accept any proposal numbered less than newProposal No along with
                // the last accepted proposal No and its value.
                response = new PrepareResponse(acceptedProposalNo, acceptedValue, true);
            }
            else
            {
                // Since new proposal No is not greater than the one accepted last time, so no promise needs to be returned
                response = new PrepareResponse(acceptedProposalNo, acceptedValue, false);
            }
            
            return response;
        }
        else
        {
            // Since this is a fresh round for this instance, lets store the responded proposal No.
            UtilityFile.writeToFile(node, PaxosInstanceFilePrefix + this.newInstanceNo + LastRespondedProposalSuffix, newProposalNo);
            return new PrepareResponse(null, null, true);
        }
    }
    
    /**
     * This method accepts the newCommand if the proposalNo is greater than the last
     * responded proposal.
     * 
     * @param paxosInstance Instance of the paxos that we are accepting command
     * @param proposalNo, the proposalNo of the new command
     * @param newCommand, the new command which needs to be accepted
     * @return the boolean indicating whether the proposal is accepted or Not.
     */
    public boolean accept(int paxosInstance, String proposalNo, PaxosCommand newCommand)
    {
        this.newInstanceNo = paxosInstance;
        
        // checks if the proposalNo is greater than the last responded proposal, then it accepts the value, otherwise no
        String oldProposalNo  = UtilityFile.readFromFile(this.node,  PaxosInstanceFilePrefix + this.newInstanceNo + LastRespondedProposalSuffix );

        if(oldProposalNo == null || new ProposalComparator().compare(proposalNo, oldProposalNo) >= 0)
        {
            // We can now accept the new proposal
            UtilityFile.writeToFile(node, PaxosInstanceFilePrefix + this.newInstanceNo + LastAcceptedProposalNoSuffix, proposalNo);
            UtilityFile.writeToDisk(node, PaxosInstanceFilePrefix + this.newInstanceNo + LastAcceptedProposalValueSuffix, newCommand);
            return true;
        }
        
        return false;
    }
}

/** This class represents the prepare request's response.
 * It contains the last proposal No,the accepted value 
 * and the promise for not accepting calls with proposalNo less than it owm
 */
class PrepareResponse implements Serializable
{
    private static final long serialVersionUID = 1L;

    /** The last proposal responded */
    String proposalNo;
    
    /** The last accepted value */
    PaxosCommand AcceptedValue;
    
    /** The promise */
    boolean promise;
    
    PrepareResponse(String proposalNo, PaxosCommand AcceptedValue, boolean promise)
    {
        this.proposalNo = proposalNo;
        this.AcceptedValue = AcceptedValue;
        this.promise = promise;
    }
    
    @Override
    public String toString()
    {
        return "No: " + proposalNo + ", Value: " + AcceptedValue + ", Promise: " + promise;
    }
}
